package ac.svn.accesscontrol.controller;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;

import ac.notification.event.INotificationListener;
import ac.notification.event.NotificationEvent;
import ac.notification.manager.NotificationRegistrar;
import ac.svn.accesscontrol.io.FileReader;
import ac.svn.accesscontrol.manager.SVNInfoManager;
import ac.svn.accesscontrol.manager.SVNTagManager;
import ac.svn.accesscontrol.store.AccessUnit;
import ac.svn.accesscontrol.store.Dictionary;
import ac.svn.accesscontrol.store.ISVNIdentifier;
import ac.svn.accesscontrol.store.ParsedItem;
import ac.svn.accesscontrol.store.SVNGroup;
import ac.svn.accesscontrol.store.SVNLocation;
import ac.svn.accesscontrol.store.SVNTag;
import ac.svn.accesscontrol.store.SVNUser;
import ac.svn.accesscontrol.utils.TagParser;
import ac.svn.accesscontrol.utils.Utility;

public class TagProcessor implements INotificationListener,ISVNIdentifier
{
	enum ETagType
	{
		COMMENT,
		TAG ,
		OPS,
		NONE
	};


	private FileReader fileReader;
	private Set<String> subscriptions;
	private SVNInfoManager infoManager;

	public TagProcessor()
	{
		fileReader = new FileReader();
		subscriptions = new HashSet<String>(5);
		infoManager = SVNInfoManager.getInfoManager();
		NotificationRegistrar.registerListener(this);
	}

	@Override
	public Set<String> declaredSubscriptionSet() {
		subscriptions.add(CMD_PROCESS_FILE);
		subscriptions.add(CMD_PROCESS_TAG);
		subscriptions.add(CMD_SAVE_FILE);
		return subscriptions;
	}

	@Override
	public void receiveEvent(NotificationEvent ne) 
	{
		if(ne.getPropertyIdentifier().contains(CMD_PROCESS_FILE))
		{
			Utility.sendNotification(N_GENERAL_INFO_MSG,"Reading file contents..");
			final String filePath = (String) ne.getPropertyValue();
			
			if(fileReader.readFile(filePath ))
			{
				//Process content of file.
				processFile();

				//Send command for assembling information from Access file and local configuration file.
				Utility.sendNotification(CMD_ASSEMBLE_INFORMATION);

				//Update title on the main window to reflect current file opened in editor.
				Utility.sendNotification(N_OPENED_FILE,filePath);

			}
			else
			{
				Utility.sendNotification(N_GENERAL_ERROR_MSG,"File Reading failed!");
				SwingUtilities.invokeLater(new Runnable() 
				{
					@Override
					public void run() 
					{
						String message = String.format("Unable to read file %s, Select another file!",filePath);
						JOptionPane.showMessageDialog(null, message, "File Reading", JOptionPane.ERROR_MESSAGE);
						Dictionary.getDictionary().setLastAccessFile(null);
						Utility.sendNotification(CMD_OPEN_FILE);
					}
				});
			}

		}
		else if(ne.getPropertyIdentifier().contains(CMD_PROCESS_TAG))
		{
			processTags();
		}
	}

	private void processFile() 
	{
		infoManager.clearAll();
		
		Utility.sendNotification(N_GENERAL_INFO_MSG,"Analyzing tags..");
		alternateTagCreator();
		
		Utility.sendNotification(N_GENERAL_INFO_MSG,"Creating data structures..");
		processTags();
		
		Utility.sendNotification(N_GENERAL_COMPLETION_MSG,"Tags creation completed!");
	}
	private void processTags()
	{
		//Parse values held in tags.
		TagParser tagParser = new  TagParser();
		//Final parsed tags.
		//Map<String, Set<String>> parsedTag = tagParser.getParsedTag();
		SVNTagManager tagsManager = SVNTagManager.getTagManager();
		for(String tagId:tagsManager.getTagIds())
		{
			SVNTag tag = tagsManager.getSvnTag(tagId);
			tagParser.parseTag(tag);

			//Update information in SVN Information Manager.
			if(TagParser.TAG_GROUP == tagParser.getTagType())
			{
				for(String grpAlias: tagParser.getParsedTag().keySet())
				{
					if((null != grpAlias) && (true != grpAlias.isEmpty()))
					{
						SVNGroup group = infoManager.getGroup(grpAlias);

						//Create new group if group does not exist.
						if(null == group )
						{
							ParsedItem parsedItem = tagParser.getParsedTag().get(grpAlias);

							group = new SVNGroup(grpAlias,grpAlias);
							group.setComments(parsedItem.getHeaderComment());

							for(String userAlias : parsedItem.getRhsEntities())
							{
								if(!userAlias.trim().isEmpty() || userAlias.trim().length() > 0)
								{
									group.addMember(userAlias.trim());
									SVNUser svnUser = infoManager.getSvnUser(userAlias.trim());
									if(null == svnUser)
									{
										svnUser = new SVNUser(userAlias.trim(), userAlias.trim());
									}
									svnUser.addGroup(grpAlias.trim());
									infoManager.updateUser(svnUser);								
								}
							}
						}
						infoManager.updateGroup(group);
					}
				}
			}
			else if(TagParser.TAG_LOCATION == tagParser.getTagType())
			{	
				String locationPath = tagParser.getTagValue();
				SVNLocation location = infoManager.getLocation(locationPath);
				//Create new group if group does not exist.
				if(null == location )
				{
					location = new SVNLocation();
					location.setLocationName(locationPath);
					location.setLocationPath(locationPath);
					location.setComments(tag.getTagHeader());

					for(String ownerId: tagParser.getParsedTag().keySet())
					{
						ParsedItem parsedItem = tagParser.getParsedTag().get(ownerId);
						Set<String> permissions = parsedItem.getRhsEntities();
						for(String perm : permissions)
						{
							AccessUnit accessUnit = null;
							if(ownerId.toUpperCase().contains("@"))
							{
								ownerId = ownerId.replace("@","");
							}
							accessUnit = new AccessUnit(ownerId);
							accessUnit.setLocationPath(locationPath);
							accessUnit.setPermission(perm);
							accessUnit.setComments(parsedItem.getHeaderComment());

							infoManager.updateAccessUnit( accessUnit);
							location.addAccessUnit(accessUnit.getAccessUnitId());
						}
					}
					infoManager.updateLocation(location);
				}

			}
		}
	}


	private void alternateTagCreator()
	{
		SVNTagManager.getTagManager().clearTags();
		List<Integer> lineNumbers = new ArrayList<Integer>(fileReader.getFileContent().keySet());
		Map<Integer,String> fileContents = fileReader.getFileContent();
		Map<String,SVNTag> tagsMap = new HashMap<String, SVNTag>();

		Collections.sort(lineNumbers);

		SVNTag svnTag = null;
		StringBuffer commentString = new StringBuffer();

		for(Integer lineNumber:lineNumbers)
		{
			String line = fileContents.get(lineNumber);
			ETagType tagType = getTagType(line);
			switch(tagType)
			{
			case TAG:
				//We are in process of creating a tag and encounter another
				//so previous tag should be completed and start a new tag.
				if(null != svnTag)
				{
					SVNTagManager.getTagManager().updateTag(svnTag.getTagId(), svnTag);
					tagsMap.put(svnTag.getTagId(), svnTag);

					//Release reference.
					svnTag = null;
				}

				//we have just encountered a TAG, start creation of same.
				svnTag = new SVNTag(line, lineNumber);

				//If we have some comment just before tag add it to tag.
				if((null != commentString) &&(false == commentString.toString().isEmpty()))
				{
					svnTag.setTagHeader(commentString.toString());
					//Reset comment string
					commentString = null;
					commentString = new StringBuffer();
				}
				break;

			case COMMENT:
				if(null != commentString)
				{
					commentString.append(line);
					if(false == commentString.toString().isEmpty())
					{
						commentString.append("\n");
					}
				}

				break;

			case OPS:
				//if an operation is encountered check, already creating a tag.
				//If any comment is present add it to tag just before adding operation.
				if(null != svnTag)
				{
					if(false ==commentString.toString().isEmpty())
					{
						svnTag.addLine(commentString.toString());
						//Reset comment string
						commentString = null;
						commentString = new StringBuffer();
					}
					svnTag.addLine(line);
				}
				break;

			default:
				break;
			}

			if(null != svnTag)
			{
				SVNTagManager.getTagManager().updateTag(svnTag.getTagId(), svnTag);
				tagsMap.put(svnTag.getTagId(), svnTag);
			}

		}
		return;
	}

	private ETagType getTagType(String line)
	{
		//remove space from boundaries.
		line = line.trim();
		//Rule 1: If line starts with # it is a comment.
		if(line.startsWith("#"))
		{
			return ETagType.COMMENT;
		}
		//Rule 2: If line contains [TAG], it represents a tag.
		else if(line.startsWith("[") && line.endsWith("]")&&(false == line.trim().startsWith("#")))
		{
			return ETagType.TAG;
		}
		//Rule 3: If line contains an '=' sign, it represents an operation
		else if(line.contains("="))
		{
			return ETagType.OPS;
		}
		else{}
		//else not sure what line represents
		return ETagType.NONE;
	}


}
